One of the most useful things a computer can do is look up an item in a dictionary. Like a simple database, a dictionary is a table with two columns.
A script looks up a key in one column and reads off the value from the other column. If the word dictionary does not seem to fit well (it does not contain any definitions), think of a Spanish-English dictionary. There are many different situations in which a quick translation from one thing to another is useful. If you are building an automatic table of contents for a stack, you might want to translate between titles of cards and card ID numbers. In a spread sheet, you might want to translate names of cells into field numbers.
Suppose you have an address stack that contains formal names like
"Robert," "William," and "Elizabeth." When you use the Find command, you really want to type nicknames like "Bob," "Bill," and "Liz." Let’s write a script that translates the informal names you type into the formal names that appear in an address stack.
A simple way to build a dictionary is to make two lists. The nicknames are separate words in one list, and the formal names are the corresponding words of the other. The lists are stored in variables. The lookup function steps through the words of the nickname list. When it finds the name you asked for, it looks for the word in the same position in the formal name list.
First let's make a function that takes a key and two lists. It looks the key up in the first list and returns a translation from the second:
function lookup key, listOfKeys, listOfAnswers
repeat with ii = 1 to the number of words of listOfKeys
if key = word ii of listOfKeys
then return word ii of listOfAnswers
end repeat
return key -- return the key if it is not in the list
end lookup
Here is the way its used:
put "Bob Bill Liz" into nickNames
put "Robert William Elizabeth" into formalNames
put lookup(word 1 of it, nickNames, formalNames) into word 1 of it
Now, we need to hook this fragment of script to the Find command in our address stack. Let’s intercept the Find command and translate the first word of the search key. (Note that the Find command actually has two arguments. The first is a number that keeps track of what kind of Find it is. This number is inserted behind our backs, and all we have to do is know to give it a name.) This script will work in all versions of HyperCard.
on find dummy,key
-- dummy is used privately by HyperTalk
send "find" && quote & key & quote to HyperCard
if the result is "not found" then
put "Bob Bill Liz" into nickNames
put "Robert William Elizabeth" into formalNames
put key into newKey
put lookup(word 1 of newKey, nickNames, formalNames) into ¬
word 1 of newKey
if newKey is not key -- if word 1 was translated
then send "find" && quote & newKey & quote to HyperCard
end if
end find
The script first sends the original Find command directly to HyperCard. If the Find succeeds, do nothing further. If the Find fails, then look up word 1 of the key and substitute it in. If this substitution changes what we are looking for, then search on the new translated version.
Next, we’ll refine this example to make it faster and more elegant.
• Let’s build a more powerful dictionary for converting a key into a value. The key may contain more than one word, and there may be multiple keys (synonyms) for the same value. We’ll make the dictionary lookup go fast by using the "offset()" function to find the key in the dictionary. Users can decide if the key should be matched completely, or if it need only match the beginning of an entry (like the Find command).
To allow multiple words in keys and values, we can’t use space as the separator between entries in the dictionary. The characters { } and | are not used very much and are unlikely to appear in the things you want to put in your dictionary, so we’ll use them as separators. A dictionary entry with several keys and a value will be a single piece of text. Every key has a { in before and after it, and the value has a | just before it and } just after it.
Here is the entry that translates nicknames for Elizabeth:
{Liz{Beth{Betty{|Elizabeth}
Don't include spaces in a dictionary entries unless you want them to be part of a key or a value. Each entry can be on a separate line if you wish.
Let's modify an address stack to translate from nicknames to formal names. We'll keep the dictionary in a global variable called "NickNames". Here is the script that creates the dictonary when you enter the stack:
on openBackground
global NickNames
put "{Bob{|Robert} {Bill{|William} {Mike{|Michael} " &
"{Liz{Beth{Betty{|Elizabeth} {Tom{|Thomas}" &
"{Jim{|James} {Dave{|David} {Joe{|Joseph}" into NickNames
end openBackground
Next, let's build a function that does the actual work of looking up a key in the dictionary. The "translate" function takes a key and a dictionary as arguments and returns the value for that key. If the key is not in the dictionary, the function returns the key unchanged.
function translate key,dictionary,exact
put "{" & key into realKey -- {Liz
if exact is empty -- look for exact match
then put offset(realKey,dictionary) into index -- loc of {Liz. . .
else put offset(realKey &"{",dictionary) into index -- loc of {Liz{
-- if the key wasn’t found, give back the original key
if index is 0 then return key -- Liz
put char index to (index+200) of dictionary into local
-- {Liz{Beth{Betty{|Elizabeth} {Tom{|Thomas...
put offset("|",local) into numStart -- 17
put offset("}",local) into numEnd -- 27
return char (numStart+1) to (numEnd-1) of local -- Elizabeth
end translate
The first half of the translate script sets "index" to the location of the
beginning of the key in the dictionary. The second half pulls out a section of the dictionary into the variable "local" in order to work on it. Currently, an entry in the dictionary can’t be longer than 200 characters
(for keys and value). To allow longer entries, change the "200" to a bigger number. The script finds the | at the beginning of the value part of the entry. Then it finds the next } at the end of the entry. It returns the characters from the beginning to the end of the value it found.
The third argument is optional. If you leave it off, the first argument only has to match the beginning of a key in the dictionary (just like the Find command). If you call translate with anything as the third argument, then the key will only be translated if it matches exactly.
Here are some sample calls:
put translate("Betty", NickNames)
put translate("Mik", NickNames,"exact")
Mik is not translated to Michael in the second example because we specified an exact match. Remember to put double-quotes around a
key if it has any spaces in it. Put the translate function in your home script, and use it with all kinds of different dictionaries.
Here's how an address stack would use translate to convert nicknames to formal names for the Find command:
on find dummy,key
-- dummy is used privately by HyperTalk
send "find" && quote & key & quote to HyperCard
if the result is "not found" then
global NickNames
put key into newKey
repeat with jj = 1 to the number of words in key
put translate(word jj of key,NickNames) into newWord
put newWord into word jj of newKey
end repeat
if newKey is key then beep
else
send "find" && quote & newKey & quote to HyperCard
if the result is "not found" then beep
end if
end if
end find
This script first tries a normal HyperCard Find on the key. If it is found, do nothing more. If the key is not found, then for each word in the key, translate it through the dictionary NickNames and substitute in the translated word. If the translation (newKey) is the same as the original
(key), then none of the words were in the dictionary. Since the first search failed, beep and exit. Otherwise, Find using the new key. If it also can’t be found, then beep. (Since we are calling Find from a script, it does not beep by itself.)
Notice that the script always replaces a word in the key with its translated value. Since words that are not in the dictionary come back from the translate function unchanged, this works.
Put the translate handler in your Home script to use from many stacks with many different dictionaries. The modified Find handler goes in an address stack. It doesn't belong in the Home script because most Finds have nothing to do with names of people. A quick translation from one set of terms to another is a very useful tool to have in your scripter’s bag of tricks. When you're building a stack and suddenly realize that a translation would help, think of these scripts.